#! /usr/local/bin/python3

import os
import subprocess
from collections import namedtuple
from tkinter import *
import tkinter.filedialog
import tkinter.messagebox
import threading
import shutil
import re
import EXTool


class ResignClass(Frame):
    def __init__(self, master):
        '''初始化窗口'''
        Frame.__init__(self, master)
        self.master = master
        self.master.title('重签名')
        # 根据文本自适应窗口大小
        self.pack(fill=BOTH, expand=1)
        self.update()

        # plist文件管理者
        self.cmd_plistBuddy = '/usr/libexec/PlistBuddy'
        # 证书管理
        self.cmd_security = '/usr/bin/security'
        # 签名管理
        self.cmd_codesign = '/usr/bin/codesign'

        # 工作目录
        self.frame_path = Frame(self.master)
        self.value_path = Variable()

        # ipa包路径
        self.frame_ipa = Frame(self.master)
        self.value_ipa = Variable()

        # 描述文件
        self.frame_provision = Frame(self.master)
        self.value_provision = Variable()
        self.team_id = ''
        self.bundle_id = ''
        self.key_sign = ''

        # keychain-access-groups
        self.frame_keychain = Frame(self.master)
        self.value_keychain = Variable()

        # ipa包名字
        self.frame_ipaName = Frame(self.master)
        self.value_ipaName = Variable()

        # 返回/签名
        self.frame_resign = Frame(self.master)

        self.view_resign()

    def view_resign(self):
        '''创建界面'''
        # 参照大小
        Size = namedtuple('Size', ['w', 'h'])
        f_size = Size(self.winfo_width()-100, 30)
        # 参照坐标
        Point = namedtuple('Point', ['x', 'y'])
        r_point = Point(50, 50)

        # 选择打包路径
        self.frame_path.config(width=f_size.w, height=f_size.h)
        Entry(self.frame_path,
              textvariable=self.value_path,
              state='disabled').place(x=0, y=0, width=f_size.w-60, height=f_size.h)
        # 设置默认路径为脚本目录
        self.value_path.set(os.path.split(sys.argv[0])[0])
        Button(self.frame_path,
               text='工作目录',
               font=('', 10),
               command=self.select_path).place(x=f_size.w-60, y=0, width=60, height=f_size.h)
        self.frame_path.place(x=r_point.x, y=r_point.y)

        # 选择ipa包
        self.frame_ipa.config(width=f_size.w, height=f_size.h)
        Entry(self.frame_ipa,
              textvariable=self.value_ipa,
              state='disabled').place(x=0, y=0, width=f_size.w-60, height=f_size.h)
        Button(self.frame_ipa,
               text='解压ipa包',
               font=('', 10),
               command=self.select_ipa).place(x=f_size.w-60, y=0, width=60, height=f_size.h)
        self.frame_ipa.place(x=r_point.x, y=r_point.y + f_size.h*1 + 10*1)

        # 选择描述文件
        self.frame_provision.config(width=f_size.w, height=f_size.h)
        Entry(self.frame_provision,
              textvariable=self.value_provision,
              state='disabled').place(x=0, y=0, width=f_size.w-60, height=f_size.h)
        Button(self.frame_provision,
               text='描述文件',
               font=('', 10),
               command=self.select_provision).place(x=f_size.w-60, y=0, width=60, height=f_size.h)
        self.frame_provision.place(x=r_point.x, y=r_point.y + f_size.h*2 + 10*2)

        # keychain-access-groups
        self.frame_keychain.config(width=f_size.w, height=f_size.h + 15)
        Label(self.frame_keychain,
              text=r'keychar-access-grous：根据需求填写，用","隔开，没有不填',
              font=('', 10), fg='red',
              anchor=tkinter.NW).place(x=0, y=0, width=f_size.w, height=15)
        Entry(self.frame_keychain,
              textvariable=self.value_keychain).place(x=0, y=15, width=f_size.w-60, height=f_size.h)
        Button(self.frame_keychain,
               text='确认配置',
               font=('', 10),
               command=self.input_keychain).place(x=f_size.w-60, y=15, width=60, height=f_size.h)
        self.frame_keychain.place(x=r_point.x, y=r_point.y + f_size.h*3 + 10*3)

        # ipa包名字
        self.frame_ipaName.config(width=f_size.w, height=f_size.h + 15)
        Label(self.frame_ipaName,
              text=r'获取命名后可以修改信息:DisplayName_ShortVersionString(CFBundleVersion)_证书类型',
              font=('', 10), fg='red',
              anchor=tkinter.NW).place(x=0, y=0, width=f_size.w, height=15)
        Entry(self.frame_ipaName,
              textvariable=self.value_ipaName).place(x=0, y=15, width=f_size.w-60, height=f_size.h)
        Button(self.frame_ipaName,
               text='获取命名',
               font=('', 10),
               command=self.get_ipa_name).place(x=f_size.w-60, y=15, width=60, height=f_size.h)
        self.frame_ipaName.place(x=r_point.x, y=r_point.y + f_size.h*4 + 15 + 10*4)

        # 返回 / 签名
        self.frame_resign.config(width=f_size.w, height=f_size.h)
        Button(self.frame_resign,
               text='返回',
               command=self.back_click).place(x=(f_size.w/2-100)/2, y=0, width=100, height=f_size.h)
        Button(self.frame_resign,
               text='开始签名',
               command=self.resign_click).place(x=f_size.w/2+(f_size.w/2-100)/2, y=0, width=100, height=f_size.h)
        self.frame_resign.place(x=r_point.x, y=r_point.y + f_size.h*5 + 15*2 + 10*7)


    def select_path(self):
        '''工作目录'''
        # askdirectory() 返回文件夹路径不是文件路径
        _path = tkinter.filedialog.askdirectory()
        if _path:
            self.value_path.set(_path)

    def callback_unzip(self, *args):
        '''ipa包解压回调处理'''
        tkinter.messagebox.showinfo('提示', args[0])
        # 找到Payload文件移动到签名目录
        if args[0] == '解压成功':
            self.value_ipa.set(args[1])
            for parent, folders, files in os.walk(self.value_path.get()):
                for folder in folders:
                    if folder == 'Payload' and parent != self.value_path.get():
                        del_folder = parent.split(self.value_path.get())[1]
                        del_folder = del_folder.split(os.sep)[1]
                        # 移动Payload
                        shutil.move(os.path.join(parent, folder), self.value_path.get())
                        # 删除不要的文件夹
                        shutil.rmtree(os.path.join(self.value_path.get(), del_folder))
        else:
            self.value_ipa.set(args[0])

    def select_ipa(self):
        '''选择ipa包'''
        if not self.value_path.get():
            tkinter.messagebox.showwarning('警告', '请选择"工作目录"')
            return

        # 删除加压目录的Payload
        if os.path.isdir(os.path.join(self.value_path.get(), 'Payload')):
            shutil.rmtree(os.path.join(self.value_path.get(), 'Payload'))

        _path = tkinter.filedialog.askopenfilename()
        if _path:
            self.value_ipa.set('解压中...')
            threading.Thread(target=EXTool.file_unzip,
                             args=(_path, self.value_path.get(), self.callback_unzip)).start()

    def select_provision(self):
        '''选择描述文件'''
        if self.value_ipa.get() == '解压中...' or self.value_ipa.get() == '解压失败' or not self.value_ipa.get():
            tkinter.messagebox.showwarning('警告', '请"解压ipa包"或等待"解压成功"')
            return

        # askopenfilename() 返回路径和文件名
        _path = tkinter.filedialog.askopenfilename()
        if _path:
            self.value_provision.set(_path)
            # 导出描述文件信息
            with open('%s/mobileprovision.plist' % self.value_path.get(), 'w') as fo:
                status, output = subprocess.getstatusoutput('%s cms -D -i %s' % (self.cmd_security,
                                                                                 EXTool.change_symbol(self.value_provision.get())))
                if status:
                    tkinter.messagebox.showerror('错误', '导出描述文件信息报错:%s\n%s' % (status, output))
                    self.value_provision.set('')
                    return
                else:
                    fo.write(output)
            # 导出签名信息
            with open('%s/entitlements.plist' % self.value_path.get(), 'w') as fo:
                status, output = subprocess.getstatusoutput('%s -x -c "Print :Entitlements" %s/mobileprovision.plist'
                                                            % (self.cmd_plistBuddy,
                                                               EXTool.change_symbol(self.value_path.get())))
                if status:
                    tkinter.messagebox.showerror('错误', '导出签名信息报错:%s\n%s' % (status, output))
                    self.value_provision.set('')
                    return
                else:
                    fo.write(output)
                    # 删除多余的文件
                    if os.path.isfile('%s/mobileprovision.plist' % self.value_path.get()):
                        os.remove('%s/mobileprovision.plist' % self.value_path.get())
            # 修改签名信息
            (status, output) = subprocess.getstatusoutput('%s -c "Print :com.apple.developer.team-identifier" %s/entitlements.plist'
                                                          % (self.cmd_plistBuddy,
                                                             EXTool.change_symbol(self.value_path.get())))
            if status:
                tkinter.messagebox.showerror('错误', '修改签名信息:获取com.apple.developer.team-identifier报错:%s\n%s'
                                             % (status, output))
                self.value_provision.set('')
                return
            else:
                self.team_id = output

            (status, output) = subprocess.getstatusoutput('%s -c "Print :CFBundleIdentifier" %s/Payload/%s/Info.plist'
                                                          % (self.cmd_plistBuddy,
                                                             EXTool.change_symbol(self.value_path.get()),
                                                             self.get_app_name()))
            if status:
                tkinter.messagebox.showerror('错误', '修改签名信息:获取CFBundleIdentifier报错:%s\n%s' % (status, output))
                self.value_provision.set('')
                return
            else:
                self.bundle_id = output

            (status, output) = subprocess.getstatusoutput('%s -c "Set :application-identifier %s.%s" %s/entitlements.plist'
                                                          % (self.cmd_plistBuddy,
                                                             self.team_id,
                                                             self.bundle_id,
                                                             EXTool.change_symbol(self.value_path.get())))
            if status:
                tkinter.messagebox.showerror('错误', '修改签名信息:设置application-identifier报错:%s\n%s'
                                             % (status, output))
                self.value_provision.set('')
                return
            else:
                pass
                # 查找钥匙串
                threading.Thread(target=self.get_key_sign).start()

    # 获取app文件名
    def get_app_name(self):
        '''获取app文件名'''
        try:
            name_list = os.listdir(os.path.join(self.value_path.get(), 'Payload'))
            for i in range(0, len(name_list)):
                if name_list[i].endswith('.app'):
                    app_name = name_list[i]
                    return app_name
        except FileNotFoundError as e:
            tkinter.messagebox.showerror('错误', e)

    # 获取钥匙串
    def get_key_sign(self):
        (status, output) = subprocess.getstatusoutput('%s find-identity -v -p codesigning | grep %s'
                                                      % (self.cmd_security, self.team_id))
        if status:
            tkinter.messagebox.showerror('错误', '钥匙串查找报错:%s\n%s' % (status, output))
        else:
            self.key_sign = output.split('"')[1]
            print('钥匙串信息:' + self.key_sign)

    # keychain-access-groups
    def input_keychain(self):
        '''keychain-access-groups'''
        if not self.value_provision.get():
            tkinter.messagebox.showwarning('警告', '请选择"描述文件"')
            return

        if self.value_keychain.get():
            keychains = str(self.value_keychain.get()).split(',')
            # 循环添加
            count = len(keychains)
            for keychain in keychains:
                status, output = subprocess.getstatusoutput(
                    '%s -c "Add :keychain-access-groups:0 string %s.%s" %s/entitlements.plist'
                    % (self.cmd_plistBuddy,
                       self.team_id,
                       keychain,
                       EXTool.change_symbol(self.value_path.get())))
                if status:
                    tkinter.messagebox.showerror('错误', '配置keychain-access-groups报错:%s\n%s' % (status, output))
                    break
                else:
                    count -= 1
            if count == 0:
                tkinter.messagebox.showinfo('提示', '配置成功')

    # 获取ipa包命名
    def get_ipa_name(self):
        '''获取ipa包命名'''
        # CFBundleDisplayName
        if not self.value_provision.get():
            tkinter.messagebox.showwarning('警告', '请选择"描述文件"')
            return

        (status, output) = subprocess.getstatusoutput('%s -c "Print :CFBundleDisplayName" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '获取命名:获取CFBundleDisplayName报错:%s\n%s' % (status, output))
            return
        else:
            self.displayName = output
        # CFBundleShortVersionString
        (status, output) = subprocess.getstatusoutput('%s -c "Print :CFBundleShortVersionString" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '获取命名:获取CFBundleShortVersionString报错:%s\n%s' % (status, output))
            return
        else:
            self.shortVersion = output
        # CFBundleVersion
        (status, output) = subprocess.getstatusoutput('%s -c "Print :CFBundleVersion" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '获取命名:获取CFBundleVersion报错:%s\n%s' % (status, output))
            return
        else:
            self.bundleVersion = output
        # Dis / Hoc
        (status, output) = subprocess.getstatusoutput('%s -c "Print :beta-reports-active" %s/entitlements.plist'
                                                      % (self.cmd_plistBuddy,
                                                         EXTool.change_symbol(self.value_path.get())))
        if status:
            self.beta = 'Hoc'
        else:
            self.beta = 'Dis'
        # 拼接新名字
        self.value_ipaName.set('%s_%s(%s)_%s' % (self.displayName, self.shortVersion, self.bundleVersion, self.beta))

    # 返回
    def back_click(self):
        self.frame_path.destroy()
        self.frame_ipa.destroy()
        self.frame_provision.destroy()
        self.frame_keychain.destroy()
        self.frame_ipaName.destroy()
        self.frame_resign.destroy()
        self.destroy()

    # 签名
    def resign_click(self):
        '''开始签名'''
        if not self.value_ipaName.get():
            tkinter.messagebox.showwarning('警告', '请"获取命名"')
            return

        # 修改Info.plist信息 DisplayName_ShortVersionString(CFBundleVersion)_证书类型
        ipa_name_list = self.value_ipaName.get().split('_')
        ipa_version_list = re.match(r'^([\d\.]+)\(([\d\.]+)\)', ipa_name_list[1])
        # CFBundleDisplayName
        (status, output) = subprocess.getstatusoutput('%s -c "Set :CFBundleDisplayName %s" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         ipa_name_list[0],
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '修改CFBundleDisplayName失败:%s\n%s' % (status, output))
            return
        # CFBundleName
        (status, output) = subprocess.getstatusoutput('%s -c "Set :CFBundleName %s" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         ipa_name_list[0],
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '修改CFBundleName失败:%s\n%s' % (status, output))
            return
        # CFBundleShortVersionString
        (status, output) = subprocess.getstatusoutput('%s -c "Set :CFBundleShortVersionString %s" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         ipa_version_list.group(1),
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '修改CFBundleShortVersionString失败:%s\n%s' % (status, output))
            return
        # CFBundleVersion
        (status, output) = subprocess.getstatusoutput('%s -c "Set :CFBundleVersion %s" %s/Payload/%s/Info.plist'
                                                      % (self.cmd_plistBuddy,
                                                         ipa_version_list.group(2),
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '修改CFBundleVersion失败:%s\n%s' % (status, output))
            return

        # 删除旧的签名文件
        if os.path.exists('%s/Payload/%s/_CodeSignature'
                          % (self.value_path.get(),
                             self.get_app_name())):
            shutil.rmtree('%s/Payload/%s/_CodeSignature'
                          % (self.value_path.get(),
                             self.get_app_name()))
        # 复制描述文件到ipa包
        shutil.copyfile(self.value_provision.get(),
                        '%s/Payload/%s/embedded.mobileprovision'
                        % (self.value_path.get(), self.get_app_name()))

        # 签名Frameworks库文件
        if os.path.isdir('%s/Payload/%s/Frameworks'
                         % (self.value_path.get(),
                            self.get_app_name())):
            framework_list = os.listdir('%s/Payload/%s/Frameworks'
                                        % (self.value_path.get(),
                                           self.get_app_name()))
            count = len(framework_list)
            for framework in framework_list:
                print('codesign:', framework)
                if framework.endswith('.framework'):
                    (status, output) = subprocess.getstatusoutput('%s -f -s "%s" %s/Payload/%s/Frameworks/%s/%s'
                                                                  % (self.cmd_codesign, self.key_sign,
                                                                     EXTool.change_symbol(self.value_path.get()),
                                                                     self.get_app_name(), framework,
                                                                     framework.split('.framework')[0]))
                    if status:
                        tkinter.messagebox.showerror('错误', '签名framework出错:%s\n%s' % (status, output))
                        break
                    else:
                        count -= 1
                elif framework.endswith('.dylib'):
                    (status, output) = subprocess.getstatusoutput('%s -f -s "%s" %s/Payload/%s/Frameworks/%s'
                                                                  % (self.cmd_codesign, self.key_sign,
                                                                     EXTool.change_symbol(self.value_path.get()),
                                                                     self.get_app_name(), framework))
                    if status:
                        tkinter.messagebox.showerror('错误', '签名dylib出错:%s\n%s' % (status, output))
                        break
                    else:
                        count -= 1
            if count:
                return
        # 签名.app文件
        (status, output) = subprocess.getstatusoutput('%s -f -s "%s" --entitlements %s/entitlements.plist %s/Payload/%s'
                                                      % (self.cmd_codesign, self.key_sign,
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         EXTool.change_symbol(self.value_path.get()),
                                                         self.get_app_name()))
        if status:
            tkinter.messagebox.showerror('错误', '签名app包出错:%s\n%s' % (status, output))
            return
        else:
            print('签名成功:', output)
        # 转换工作路径
        os.chdir(self.value_path.get())
        threading.Thread(target=EXTool.file_zip, args=('./Payload',
                                                       './%s.ipa' % self.value_ipaName.get(),
                                                       self.callback_zip)).start()

    # 压缩回调
    def callback_zip(self, message):
        if message == '压缩成功':
            tkinter.messagebox.showinfo('提示', '签名成功')
            print('finder')

